package com.storm.config.jackson

import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.databind.Module
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.PropertyNamingStrategy
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer
import org.springframework.beans.BeanUtils
import org.springframework.beans.factory.BeanFactoryUtils
import org.springframework.beans.factory.ListableBeanFactory
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer
import org.springframework.boot.autoconfigure.jackson.JacksonProperties
import org.springframework.context.ApplicationContext
import org.springframework.core.Ordered
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder
import org.springframework.util.Assert
import org.springframework.util.ClassUtils
import org.springframework.util.ReflectionUtils
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.time.format.DateTimeFormatter

class StormJackson2ObjectMapperBuilderCustomizer(private val applicationContext: ApplicationContext,
                                                 private val jacksonProperties: JacksonProperties,
                                                 private val stormJacksonProperties: StormJacksonProperties) : Jackson2ObjectMapperBuilderCustomizer, Ordered {

    companion object {
        private var FEATURE_DEFAULTS = HashMap<SerializationFeature, Boolean>()

        private fun <T> getBeans(beanFactory: ListableBeanFactory, type: Class<T>): Collection<T> {
            return BeanFactoryUtils.beansOfTypeIncludingAncestors(beanFactory, type).values
        }

        init {
            FEATURE_DEFAULTS[SerializationFeature.WRITE_DATES_AS_TIMESTAMPS] = false
        }
    }

    override fun getOrder(): Int {
        return 0
    }

    override fun customize(builder: Jackson2ObjectMapperBuilder) {
        if (jacksonProperties.defaultPropertyInclusion != null) {
            builder.serializationInclusion(jacksonProperties.defaultPropertyInclusion)
        }
        if (jacksonProperties.timeZone != null) {
            builder.timeZone(jacksonProperties.timeZone)
        }
        builder.simpleDateFormat(stormJacksonProperties.dateTimeFormat)
        builder.serializers(LocalDateSerializer(DateTimeFormatter.ofPattern(stormJacksonProperties.dateFormat)))
        builder.serializers(LocalDateTimeSerializer(DateTimeFormatter.ofPattern(stormJacksonProperties.dateTimeFormat)))
        builder.serializers(LocalTimeSerializer(DateTimeFormatter.ofPattern(stormJacksonProperties.timeFormat)))
        builder.deserializers(LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(stormJacksonProperties.dateTimeFormat)))
        builder.deserializers(LocalDateDeserializer(DateTimeFormatter.ofPattern(stormJacksonProperties.dateFormat)))
        builder.deserializers(LocalTimeDeserializer(DateTimeFormatter.ofPattern(stormJacksonProperties.timeFormat)))
        configureFeatures(builder, FEATURE_DEFAULTS)
        configureVisibility(builder, jacksonProperties.visibility)
        configureFeatures(builder, jacksonProperties.deserialization)
        configureFeatures(builder, jacksonProperties.serialization)
        configureFeatures(builder, jacksonProperties.mapper)
        configureFeatures(builder, jacksonProperties.parser)
        configureFeatures(builder, jacksonProperties.generator)
        configureDateFormat(builder)
        configurePropertyNamingStrategy(builder)
        configureModules(builder)
        configureLocale(builder)
    }

    private fun configureFeatures(builder: Jackson2ObjectMapperBuilder, features: Map<*, Boolean>) {
        features.forEach { (feature: Any?, value: Boolean?) ->
            if (value) {
                builder.featuresToEnable(feature)
            } else {
                builder.featuresToDisable(feature)
            }
        }
    }

    private fun configureVisibility(builder: Jackson2ObjectMapperBuilder, visibilities: Map<PropertyAccessor, JsonAutoDetect.Visibility>) {
        visibilities.forEach { (accessor: PropertyAccessor?, visibility: JsonAutoDetect.Visibility?) -> builder.visibility(accessor, visibility) }
    }

    private fun configureDateFormat(builder: Jackson2ObjectMapperBuilder) {
        val dateFormat = jacksonProperties.dateFormat
        if (dateFormat != null) {
            try {
                val dateFormatClass = ClassUtils.forName(dateFormat, null)
                builder.dateFormat(BeanUtils.instantiateClass(dateFormatClass) as DateFormat)
            } catch (var6: ClassNotFoundException) {
                val simpleDateFormat = SimpleDateFormat(dateFormat)
                var timeZone = jacksonProperties.timeZone
                if (timeZone == null) {
                    timeZone = ObjectMapper().serializationConfig.timeZone
                }
                simpleDateFormat.timeZone = timeZone
                builder.dateFormat(simpleDateFormat)
            }
        }
    }

    private fun configurePropertyNamingStrategy(builder: Jackson2ObjectMapperBuilder) {
        val strategy = jacksonProperties.propertyNamingStrategy
        if (strategy != null) {
            try {
                configurePropertyNamingStrategyClass(builder, ClassUtils.forName(strategy, null))
            } catch (var4: ClassNotFoundException) {
                configurePropertyNamingStrategyField(builder, strategy)
            }
        }
    }

    private fun configurePropertyNamingStrategyClass(builder: Jackson2ObjectMapperBuilder, propertyNamingStrategyClass: Class<*>) {
        builder.propertyNamingStrategy(BeanUtils.instantiateClass(propertyNamingStrategyClass) as PropertyNamingStrategy)
    }

    private fun configurePropertyNamingStrategyField(builder: Jackson2ObjectMapperBuilder, fieldName: String) {
        val field = ReflectionUtils.findField(PropertyNamingStrategy::class.java, fieldName, PropertyNamingStrategy::class.java)
        Assert.notNull(field) { "Constant named '" + fieldName + "' not found on " + PropertyNamingStrategy::class.java.name }
        try {
            builder.propertyNamingStrategy(field[null] as PropertyNamingStrategy)
        } catch (e: Exception) {
            throw IllegalStateException(e)
        }
    }

    private fun configureModules(builder: Jackson2ObjectMapperBuilder) {
        val moduleBeans = getBeans(applicationContext, Module::class.java)
        builder.modulesToInstall(*moduleBeans.toTypedArray())
    }

    private fun configureLocale(builder: Jackson2ObjectMapperBuilder) {
        val locale = jacksonProperties.locale
        if (locale != null) {
            builder.locale(locale)
        }
    }

}